#ifndef __CLOUD_UTIL__
#define __CLOUD_UTIL__

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <filesystem>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "bundle.h"
#include "jsoncpp/json/json.h"

namespace cloud
{
    namespace fs = std::filesystem;

    class FileUtil
    {
    private:
        std::string _filename;

    public:
        FileUtil(const std::string& filename): _filename(filename) {}

        int64_t FileSize()
        {
            struct stat st;
            if(stat(_filename.c_str(), &st) != 0)
            {
                std::cerr << "get file size failed\n";
                return -1;
            }
            return st.st_size;
        }

        time_t LastATime()
        {
            struct stat st;
            if(stat(_filename.c_str(), &st) != 0)
            {
                std::cerr << "get file atime failed\n";
                return -1;
            }
            return st.st_atime;
        }

        time_t LastMTime()
        {
            struct stat st;
            if(stat(_filename.c_str(), &st) != 0)
            {
                std::cerr << "get file mtime failed\n";
                return -1;
            }
            
            return st.st_mtime;
        }


        std::string FileName()
        {
            return fs::path(_filename).filename().string();
        }


        bool SetContent(const std::string& content)
        {
            std::ofstream ofs;
            ofs.open(_filename, std::ios::binary);
            if(!ofs.is_open())
            {
                std::cerr << "write open file failed!\n";
                return false;
            }
            ofs.write(&content[0], content.size());
            if(!ofs.good())
            {
                std::cerr << "write file contenet failed!\n";
                ofs.close();
                return false;
            }
            ofs.close();
            return true;
        }

        bool GetContent(std::string* content)
        {
            return GetPosLenContent(content, 0, FileSize());
        }

        bool GetPosLenContent(std::string* content, size_t pos, size_t len)
        {
            if(pos + len > FileSize())
            {
                std::cerr << "get file len is error\n";
                return false;
            }

            std::ifstream ifs;
            ifs.open(_filename, std::ios::binary);
            if(!ifs.is_open())
            {
                std::cerr << "open file failed\n";
                return false;
            }

            ifs.seekg(pos, std::ios::beg);
            content->resize(len);
            ifs.read(&(*content)[0], len);
            if(!ifs.good())
            {
                std::cerr << "get file content failed!\n";
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }



        bool Exists()
        {
            return fs::exists(_filename);
        }

        bool CreateParentDir()
        {
            FileUtil fu(fs::path(_filename).parent_path().string());
            return fu.CreateDir();
        }

        bool CreateDir()
        {
            if(Exists())
                return true;
            return fs::create_directories(_filename);
        }

        bool RetrieveFilenames(std::vector<std::string>* array)
        {
            for(auto& p: fs::directory_iterator(_filename))
            {
                if(fs::is_directory(p))
                {
                    continue;
                }
                array->push_back(fs::path(p).relative_path().string());
            }
            return true;
        }

        bool Delete()
        {
            return fs::remove(_filename);
        }

        bool Compress(const std::string& packname)
        {
            std::string content;
            if(!GetContent(&content))
            {
                std::cerr << "compress get file failed!\n";
                return false;
            }
            std::string packed = bundle::pack(bundle::ZSTDF, content);
            
            FileUtil fu(packname);
            if(!fu.SetContent(packed))
            {
                std::cerr << "compress write packed data failed!\n";
                return false;
            }
            return true;
        }

        bool Uncompress(const std::string& filename)
        {
            std::string content;
            if(!GetContent(&content))
            {
                std::cerr << "uncompress get file content failed!\n";
                return false;
            }
            std::string unpacked = bundle::unpack(content);
            FileUtil fu(filename);
            if(!fu.SetContent(unpacked))
            {
                std::cerr << "uncompress write unpacked data failed!\n";
                return false;
            }
            return true;
        }


    };



    class JsonUtil
    {
    public:
        static bool Serialize(Json::Value& root, std::string* str)
        {
            Json::StreamWriterBuilder swBuilder;
            std::unique_ptr<Json::StreamWriter> sw(swBuilder.newStreamWriter());
            std::stringstream ss;
            if(sw->write(root, &ss) != 0)
            {
                std::cerr << "json write failed!\n";
                return false;
            }
            *str = ss.str();
            return true;
        }

        static bool Unserialize(std::string& str, Json::Value* root)
        {
            Json::CharReaderBuilder crBuilder;
            std::unique_ptr<Json::CharReader> cr(crBuilder.newCharReader());
            std::string err;
            
            if(!cr->parse(&str[0], &str[0] + str.size(), root, &err))
            {
                std::cerr << "parse error:" << err << std::endl;
                return false;
            }

            return true;
        }
    };
}




#endif
